<?php
/**
 * @link https://ym2.cleverstone.top
 * @copyright Copyright (c) 2020 Yii Manager Software LLC
 */

namespace builder\menu;

use builder\base\Builder;
use builder\base\ViewBuilderInterface;
use builder\helper\App;
use builder\helper\Arr;
use builder\helper\RegEx;
use builder\helper\Url;
use builder\menu\util\menu_toolbar\MenuToolbarType;
use builder\menu\util\row_events\RowEventType;
use yii\base\InvalidArgumentException;
use yii\web\AssetBundle;
use yii\web\View;

/**
 * 菜单组件构建器
 * ------------------- html -------------------
 * @property-read string $name 组件名
 * @property bool $partial 是否局部继承
 * @property string $ajaxRoute ajax路由
 * @property array $rootLayout 根布局
 * @property array $menuLayout 树布局
 * @property array $rowEvents 行事件
 * @property array $menuToolbar 菜单工具栏
 * @property array $behaviorCall 行为调用
 * @property array $extraJs 额外的JS
 * @property array $extraCss 额外的CSS
 * @property array $extraAssetBundle 额外的静态包
 * @property boolean $autoRequest 是否自动发起数据请求
 * ------------------- ajax -------------------
 * @property array|\Closure $data 菜单数据
 * @author cleverstone
 * @since ym2.0
 */
class MenuBuilder extends Builder implements ViewBuilderInterface
{
    /**
     * The location of registered html or text.
     * This means the location is in the menu head section.
     */
    const POS_HEAD = 1;
    /**
     * The location of registered html or text.
     * This means the location is at the end of the menu section.
     */
    const POS_END = 2;
    /**
     * @var bool 是否局部继承
     * @see $partial
     * @see setPartial()
     * @see getPartial()
     */
    private $_partial = false;
    /**
     * @var array Ajax路由
     * @see $ajaxRoute
     * @see setAjaxRoute()
     * @see getAjaxRoute()
     */
    private $_ajaxRoute = [];
    /**
     * @var array 根布局
     * @see $rootLayout
     * @see setRootLayout()
     * @see getRootLayout()
     */
    private $_rootLayout = [];
    /**
     * @var array 菜单布局
     * @see $treeLayout
     * @see setMenuLayout()
     * @see getMenuLayout()
     */
    private $_menuLayout = [];
    /**
     * @var array 行事件
     * @see $rowEvents
     * @see setRowEvents()
     * @see getRowEvents()
     */
    private $_rowEvents = [];
    /**
     * @var array 菜单工具栏
     * @see $menuToolbar
     * @see setMenuToolbar()
     * @see getMenuToolbar()
     */
    private $_menuToolbar = [];
    /**
     * @var array 行为调用
     * @see $behaviorCall
     * @see setBehaviorCall()
     * @see getBehaviorCall()
     */
    private $_behaviorCall = [];
    /**
     * @var array 额外的JS
     * ```php
     * $table->extraCss = <<<JS
     * console.log("测试一下 [setExtraCss] 方法;")
     * JS;
     *
     * ```
     * @see $extraJs
     * @see setExtraJs()
     * @see getExtraJs()
     */
    private $_extraJs = [];
    /**
     * @var array 额外的CSS
     * ```php
     * $table->extraCss = <<<CSS
     * body{background-color: #fff;}
     * CSS;
     *
     * ```
     * @see $extraCss
     * @see setExtraCss()
     * @see getExtraCss()
     */
    private $_extraCss = [];
    /**
     * @var array 额外的静态资源包
     * ```php
     * $table->extraAssetBundle = MemberAsset::class;
     *
     * ```
     * @see $extraAssetBundle
     * @see setExtraAssetBundle()
     * @see getExtraAssetBundle()
     */
    private $_extraAssetBundle = [];
    /**
     * @var bool 是否自动发起数据请求
     * @see $autoRequest
     * @see setAutoRequest()
     * @see getAutoRequest()
     */
    private $_autoRequest = true;
    /**
     * @var array|\Closure ajax数据
     * @see $data
     * @see setData()
     * @see getData()
     */
    private $_data = [];
    /**
     * @var array menu切点内容
     * - key int 切点位置
     * - value array 切点html或text内容
     */
    private $_pointcutBody = [];
    /**
     * @var int 事件计数器
     */
    private static $_eventCounter = 0;

    /**
     * 设置视图局部继承
     * @param bool $boolean
     * @return $this
     */
    public function setPartial($boolean = true)
    {
        $this->_partial = $boolean;
        return $this;
    }

    /**
     * @return bool
     */
    public function getPartial()
    {
        return $this->_partial;
    }

    /**
     * 设置AJax路由
     * ```php
     * //数据返回
     * [
     *  ['id' => 1, 'title' => '商品分类', 'children' => [], 'spread' => true],
     *  ['id' => 2, 'title' => '热门商品', 'children' => [], 'spread' => true],
     *  ['id' => 3, 'title' => '折扣商品', 'children' => [], 'spread' => true],
     *  ['id' => 4, 'title' => '订单标记', 'children' => [], 'spread' => true],
     *  ['id' => 5, 'title' => '网络热门', 'children' => [], 'spread' => true],
     *  ['id' => 6, 'title' => '我的最爱', 'children' => [], 'spread' => true],
     *  ['id' => 7, 'title' => '其他标签', 'children' => [], 'spread' => true],
     * ]
     * ```
     * @param string $route 路由
     * @param array $params 参数
     * @return $this
     */
    public function setAjaxRoute($route, $params = [])
    {
        $this->_ajaxRoute = [
            'route' => $route,
            'params' => (array)$params,
        ];
        return $this;
    }

    /**
     * @return string
     */
    public function getAjaxRoute()
    {
        $params = [
            $this->idParam => $this->id,
            App::params('http.ajax.identity') => 1,
        ];
        $ajaxRoute = $this->_ajaxRoute;
        if (empty($ajaxRoute)) {
            return Url::current($params, '');
        } elseif (empty($ajaxRoute['route'])) {
            $params = array_replace_recursive($ajaxRoute['params'], $params);
            return Url::current($params, '');
        } else {
            $ajaxRoute['params'][0] = $ajaxRoute['route'];
            $params = array_replace_recursive($ajaxRoute['params'], $params);

            return Url::to($params, '');
        }
    }

    /**
     * 设置根布局
     * @param array $options
     * @return $this
     */
    public function setRootLayout(array $options)
    {
        if (!empty($options['class'])) {
            $this->formatUiClass($options['class']);
        } else {
            $options['class'] = '';
        }

        if (!empty($options['style'])) {
            $this->formatStyle($options['style']);
        } else {
            $options['style'] = '';
        }

        if (!empty($options['attribute'])) {
            $this->formatHtmlAttribute($options['attribute']);
        } else {
            $options['attribute'] = '';
        }

        $this->_rootLayout = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getRootLayout()
    {
        if (empty($this->_rootLayout)) {
            return [
                'style' => '',
                'class' => 'w-200 h-100',
                'attribute' => '',
            ];
        }

        return $this->_rootLayout;
    }

    /**
     * 设置菜单布局
     * @param array $options
     * @return $this
     */
    public function setMenuLayout(array $options)
    {
        if (!empty($options['class'])) {
            $this->formatUiClass($options['class']);
        } else {
            $options['class'] = '';
        }

        if (!empty($options['style'])) {
            $this->formatStyle($options['style']);
        } else {
            $options['style'] = '';
        }

        if (!empty($options['attribute'])) {
            $this->formatHtmlAttribute($options['attribute']);
        } else {
            $options['attribute'] = '';
        }

        $this->_menuLayout = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getMenuLayout()
    {
        if (empty($this->_menuLayout)) {
            return [
                'style' => '',
                'class' => '',
                'attribute' => '',
            ];
        }

        return $this->_menuLayout;
    }

    /**
     * 设置行事件
     * @param array $rowEvents
     * @return $this
     */
    public function setRowEvents(array $rowEvents)
    {
        $options = [];
        foreach ($rowEvents as $item) {
            if (is_callable($item)) {
                $item = call_user_func($item);
            } elseif ($item instanceof RowEventType) {
                $item = $item->_toArray();
            }

            if (!is_array($item)) {
                throw new InvalidArgumentException('Expected parameter type is array, But the others are given. ');
            }

            if (!empty($item)) {
                $item['route'] = $item['route'] ? Url::to($item['route'], '') : Url::current([], '');
                $item['id'] = $this->_generateEventId();
                $options[] = $item;
            }
        }

        $this->_rowEvents = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getRowEvents()
    {
        return $this->_rowEvents;
    }

    /**
     * 设置菜单工具栏
     * @param array $toolbars
     * @return $this
     */
    public function setMenuToolbar(array $toolbars)
    {
        $options = [];
        foreach ($toolbars as $item) {
            if (is_callable($item)) {
                $item = call_user_func($item);
            } elseif ($item instanceof MenuToolbarType) {
                $item = $item->_toArray();
            }

            if (!is_array($item)) {
                throw new InvalidArgumentException('Expected parameter type is array, But the others are given.');
            }

            if (!empty($item)) {
                $item['route'] = $item['route'] ? Url::to($item['route'], '') : Url::current([], '');
                $item['__event_id'] = $this->_generateEventId();
                $options[] = $item;
            }
        }

        $this->_menuToolbar = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getMenuToolbar()
    {
        return Arr::index($this->_menuToolbar, null, 'pos');
    }

    /**
     * 设置行为调用
     * @param string $behaviorId 行为ID, 即:挂载在[window.ym2BehaviorReqExport]下的属性名
     * @param array $params 筛选参数 ['id'] ['alias' => ':id'] ['id' => 1]
     * @return $this
     */
    public function setBehaviorCall($behaviorId, $params = [])
    {
        $this->_behaviorCall[$behaviorId] = (array)$params;
        return $this;
    }

    /**
     * @return array
     */
    public function getBehaviorCall()
    {
        return $this->_behaviorCall;
    }

    /**
     * 设置额外的JS
     * @param array|string $js 额外的JS脚本
     * @return $this
     */
    public function setExtraJs($js)
    {
        $js = (array)$js;
        foreach ($js as &$value) {
            if (is_callable($value)) {
                $value = call_user_func($value);
            }
        }

        $this->_extraJs = array_merge($this->_extraJs, $js);
        return $this;
    }

    /**
     * 获取额外的JS
     * @return array
     */
    public function getExtraJs()
    {
        return $this->_extraJs;
    }

    /**
     * 设置额外的CSS
     * @param array|string $css 额外的CSS脚本
     * @return $this
     */
    public function setExtraCss($css)
    {
        $css = (array)$css;
        foreach ($css as &$value) {
            if (is_callable($value)) {
                $value = call_user_func($value);
            }
        }

        $this->_extraCss = array_merge($this->_extraCss, $css);
        return $this;
    }

    /**
     * 获取额外的CSS
     * @return array
     */
    public function getExtraCss()
    {
        return $this->_extraCss;
    }

    /**
     * 设置额外的静态资源包
     * @param string|array $extraAssetBundle 静态资源包
     * @return $this
     */
    public function setExtraAssetBundle($extraAssetBundle)
    {
        $extraAssetBundle = (array)$extraAssetBundle;

        // 检查
        foreach ($extraAssetBundle as $assetBundle) {
            $tempObj = new $assetBundle;
            if (!$tempObj instanceof AssetBundle) {
                throw new InvalidArgumentException($assetBundle . ' is not instance of ' . AssetBundle::class);
            }
        }

        $this->_extraAssetBundle = array_merge($this->_extraAssetBundle, $extraAssetBundle);
        return $this;
    }

    /**
     * 获取额外的静态资源包
     * @return array
     */
    public function getExtraAssetBundle()
    {
        return $this->_extraAssetBundle;
    }

    /**
     * 是否自动发起数据请求
     * @param bool $boolean
     * @return $this
     */
    public function setAutoRequest($boolean = true)
    {
        $this->_autoRequest = (bool)$boolean;
        return $this;
    }

    /**
     * @return bool
     */
    public function getAutoRequest()
    {
        return $this->_autoRequest;
    }

    /**
     * 设置ajax返回数据
     * @param array|\Closure $data
     * @return $this
     */
    public function setData($data)
    {
        if (!is_array($data) && !is_callable($data)) {
            throw new \InvalidArgumentException('Expected parameter type is array or callable, But the others are given.');
        }

        $this->_data = $data;
        return $this;
    }

    /**
     * @return array
     */
    public function getData()
    {
        if (is_callable($this->_data)) {
            return call_user_func($this->_data);
        }

        return $this->_data;
    }

    /**
     * 注册tree组件切点
     * @param string $content 内容
     * @param int $pos 切点
     * @return $this
     */
    public function registerPointcut($content, $pos = self::POS_HEAD)
    {
        $this->_pointcutBody[$pos][] = (string)$content;
        return $this;
    }

    /**
     * 开始菜单
     * @return null|string
     */
    public function beginMenu()
    {
        if (empty($this->_pointcutBody[self::POS_HEAD])) {
            return null;
        }

        return implode("\n", $this->_pointcutBody[self::POS_HEAD]);
    }

    /**
     * 结束菜单
     * @return null|string
     */
    public function endMenu()
    {
        if (empty($this->_pointcutBody[self::POS_END])) {
            return null;
        }

        return implode("\n", $this->_pointcutBody[self::POS_END]);
    }

    /**
     * 实例化
     * @param array $config
     * @return MenuBuilder
     */
    public static function instance($config = [])
    {
        return new static($config);
    }

    /**
     * @return string the view path that may be prefixed to a relative view name.
     */
    public function getViewPath()
    {
        return __DIR__;
    }

    /**
     * 构建器名
     * @return string
     */
    public function getName()
    {
        return 'menu';
    }

    /**
     * 菜单组件渲染
     * @param bool $onlyRenderContent 是否仅渲染内容
     * @return string
     * @throws \yii\base\InvalidConfigException
     */
    public function render($onlyRenderContent = false)
    {
        if ($this->isAjax) {//AJAX
            return $this->asOk('Success', $this->data);
        } else {//HTML
            $content = $this->_renderHtml();

            // 仅内容渲染
            if ($onlyRenderContent) {
                return $content;
            }

            // 局部布局渲染(不包含: 菜单和导航栏)
            if ($this->partial) {
                return $this->_view->renderFile($this->partialPath, ['content' => $content], $this);
            }

            // 主布局渲染(包含: 菜单和导航栏)
            return $this->_view->renderFile($this->mainPath, ['content' => $content], $this);
        }
    }

    /**
     * 渲染HTML
     * @return string
     * @throws \yii\base\InvalidConfigException
     */
    private function _renderHtml()
    {
        // 注册JS脚本
        $this->_view->registerJs($this->_resolveJsScript(), View::POS_END);

        // 注册额外CSS脚本
        if (!empty($this->extraCss)) {
            $this->_view->registerCss(implode("\n", $this->extraCss));
        }

        // 注册额外的AssetBundle
        if (!empty($this->extraAssetBundle)) {
            foreach ($this->extraAssetBundle as $assetBundle) {
                $this->_view->registerAssetBundle($assetBundle);
            }
        }

        return $this->_view->render('view', [
            // view.php中参数注册
            'rootLayout' => $this->rootLayout,//根布局
            'menuLayout' => $this->menuLayout,//菜单布局
            'menuToolbar' => $this->menuToolbar,//工具栏分组

        ], $this);
    }

    /**
     * 解析树组件的JS脚本
     * @return string
     */
    private function _resolveJsScript()
    {
        $jsScripts = $this->_view->render('app', [
            'extraJs' => $this->extraJs,//额外的JS脚本
            'ajaxRoute' => $this->ajaxRoute,//ajax URL
            'rowEvents' => $this->rowEvents,//行事件
            'behaviorCall' => $this->behaviorCall,//行为调用
            'autoRequest' => $this->autoRequest,//是否自动加载

        ], $this);

        return RegEx::js($jsScripts);
    }

    /**
     * 生成事件ID
     * @return string
     */
    private function _generateEventId()
    {
        self::$_eventCounter++;
        return 'ym2_menu_event' . self::$_eventCounter;
    }
}